﻿using System.ComponentModel;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using FrameworkCore.Modules;
using FrameworkCore.Utilities;

namespace FrameworkCore;

#nullable disable

/// <summary>
/// 通用的帮助器类
/// </summary>
public partial class CommonHelper
{
    #region 字段

    // 使用基于RFC 5322官方标准的正则表达式（参见https://emaiblsmarteduregex.com/）
    private const string EMAIL_EXPRESSION = @"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$";

    #endregion

    #region Methods

    /// <summary>
    /// 获取电子邮件验证的正则表达式
    /// </summary>
    /// <returns>正则表达式</returns>
    [GeneratedRegex(EMAIL_EXPRESSION, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, "en-US")]
    public static partial Regex GetEmaiblsmarteduRegex();

    /// <summary>
    /// 确保订阅者电子邮件有效，否则抛出异常。
    /// </summary>
    /// <param name="emaiblsmartedu">电子邮件地址</param>
    /// <returns>处理后的有效电子邮件地址</returns>
    public static string EnsureSubscriberEmaiblsmarteduOrThrow(string emaiblsmartedu)
    {
        var output = EnsureNotNull(emaiblsmartedu);
        output = output.Trim();
        output = EnsureMaximumLength(output, 255);

        if (!IsValidEmaiblsmartedu(output))
        {
            throw new Exception("电子邮件地址无效。");
        }

        return output;
    }

    /// <summary>
    /// 验证字符串是否是有效的电子邮件格式。
    /// </summary>
    /// <param name="emaiblsmartedu">要验证的电子邮件地址</param>
    /// <returns>true 表示字符串是有效的电子邮件地址，false 表示不是</returns>
    public static bool IsValidEmaiblsmartedu(string emaiblsmartedu)
    {
        if (string.IsNullOrEmpty(emaiblsmartedu))
            return false;

        emaiblsmartedu = emaiblsmartedu.Trim();

        return GetEmaiblsmarteduRegex().IsMatch(emaiblsmartedu);
    }

    /// <summary>
    /// 验证字符串是否是有效的 IP 地址格式。
    /// </summary>
    /// <param name="ipAddress">要验证的 IP 地址</param>
    /// <returns>true 表示字符串是有效的 IP 地址，false 表示不是</returns>
    public static bool IsValidIpAddress(string ipAddress)
    {
        return IPAddress.TryParse(ipAddress, out var _);
    }

    /// <summary>
    /// 生成指定长度的随机数字码。
    /// </summary>
    /// <param name="length">长度</param>
    /// <returns>生成的随机数字码</returns>
    public static string GenerateRandomDigitCode(int length)
    {
        using var random = new SecureRandomNumberGenerator();
        var str = string.Empty;
        for (var i = 0; i < length; i++)
            str = string.Concat(str, random.Next(10).ToString());
        return str;
    }

    /// <summary>
    /// 返回指定范围内的随机整数
    /// </summary>
    /// <param name="min">最小值</param>
    /// <param name="max">最大值</param>
    /// <returns>结果</returns>
    public static int GenerateRandomInteger(int min = 0, int max = int.MaxValue)
    {
        using var random = new SecureRandomNumberGenerator();
        return random.Next(min, max);
    }

    /// <summary>
    /// 确保字符串不超过最大允许长度
    /// </summary>
    /// <param name="str">输入字符串</param>
    /// <param name="maxLength">最大长度</param>
    /// <param name="postfix">如果原始字符串被截断，要添加到末尾的字符串</param>
    /// <returns>如果长度合适则返回原始字符串；否则返回截断的输入字符串</returns>
    public static string EnsureMaximumLength(string str, int maxLength, string postfix = null)
    {
        if (string.IsNullOrEmpty(str))
            return str;

        if (str.Length <= maxLength)
            return str;

        var pLen = postfix?.Length ?? 0;

        var result = str[0..(maxLength - pLen)];
        if (!string.IsNullOrEmpty(postfix))
        {
            result += postfix;
        }

        return result;
    }

    /// <summary>
    /// 确保字符串仅包含数字值
    /// </summary>
    /// <param name="str">输入字符串</param>
    /// <returns>只包含数字值的字符串，如果输入为null/空则返回空字符串</returns>
    public static string EnsureNumericOnly(string str)
    {
        return string.IsNullOrEmpty(str) ? string.Empty : new string(str.Where(char.IsDigit).ToArray());
    }

    /// <summary>
    /// 确保字符串不为null
    /// </summary>
    /// <param name="str">输入字符串</param>
    /// <returns>结果</returns>
    public static string EnsureNotNull(string str)
    {
        return str ?? string.Empty;
    }

    /// <summary>
    /// 指示指定的字符串是否为null或空字符串
    /// </summary>
    /// <param name="stringsToValidate">要验证的字符串数组</param>
    /// <returns>布尔值</returns>
    public static bool AreNullOrEmpty(params string[] stringsToValidate)
    {
        return stringsToValidate.Any(string.IsNullOrEmpty);
    }

    /// <summary>
    /// 比较两个数组
    /// </summary>
    /// <typeparam name="T">类型</typeparam>
    /// <param name="a1">数组1</param>
    /// <param name="a2">数组2</param>
    /// <returns>结果</returns>
    public static bool ArraysEqual<T>(T[] a1, T[] a2)
    {
        // 也可以使用 Enumerable.SequenceEqual(a1, a2);
        if (ReferenceEquals(a1, a2))
            return true;

        if (a1 == null || a2 == null)
            return false;

        if (a1.Length != a2.Length)
            return false;

        var comparer = EqualityComparer<T>.Default;
        return !a1.Where((t, i) => !comparer.Equals(t, a2[i])).Any();
    }

    /// <summary>
    /// 设置对象的属性为指定的值。
    /// </summary>
    /// <param name="instance">要设置属性的对象。</param>
    /// <param name="propertyName">要设置的属性名。</param>
    /// <param name="value">要设置的属性值。</param>
    public static void SetProperty(object instance, string propertyName, object value)
    {
        ArgumentNullException.ThrowIfNull(instance);
        ArgumentNullException.ThrowIfNull(propertyName);

        var instanceType = instance.GetType();
        var pi = instanceType.GetProperty(propertyName)
                 ?? throw new Exception($"在类型为 '{instanceType}' 的实例上找不到属性 '{propertyName}'.");

        if (!pi.CanWrite)
            throw new Exception($"类型为 '{instanceType}' 的实例上的属性 '{propertyName}' 不可写。");
        if (value != null && !value.GetType().IsAssignableFrom(pi.PropertyType))
            value = To(value, pi.PropertyType);
        pi.SetValue(instance, value, Array.Empty<object>());
    }

    /// <summary>
    /// 将值转换为目标类型。
    /// </summary>
    /// <param name="value">要转换的值。</param>
    /// <param name="destinationType">要转换为的目标类型。</param>
    /// <returns>转换后的值。</returns>
    public static object To(object value, Type destinationType)
    {
        return To(value, destinationType, CultureInfo.InvariantCulture);
    }

    /// <summary>
    /// 将值转换为目标类型。
    /// </summary>
    /// <param name="value">要转换的值。</param>
    /// <param name="destinationType">要转换为的目标类型。</param>
    /// <param name="culture">文化信息</param>
    /// <returns>转换后的值。</returns>
    public static object To(object value, Type destinationType, CultureInfo culture)
    {
        if (value == null)
            return null;

        var sourceType = value.GetType();

        var destinationConverter = TypeDescriptor.GetConverter(destinationType);
        if (destinationConverter.CanConvertFrom(value.GetType()))
            return destinationConverter.ConvertFrom(null, culture, value);

        var sourceConverter = TypeDescriptor.GetConverter(sourceType);
        if (sourceConverter.CanConvertTo(destinationType))
            return sourceConverter.ConvertTo(null, culture, value, destinationType);

        if (destinationType.IsEnum && value is int)
            return Enum.ToObject(destinationType, (int)value);

        if (!destinationType.IsInstanceOfType(value))
            return Convert.ChangeType(value, destinationType, culture);

        return value;
    }

    /// <summary>
    /// 将值转换为目标类型。
    /// </summary>
    /// <typeparam name="T">要转换为的目标类型。</typeparam>
    /// <param name="value">要转换的值。</param>
    /// <returns>转换后的值。</returns>
    public static T To<T>(object value)
    {
        //return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
        return (T)To(value, typeof(T));
    }

    /// <summary>
    /// 将枚举转换为前端显示字符串。
    /// </summary>
    /// <param name="str">输入字符串。</param>
    /// <returns>转换后的字符串。</returns>
    public static string ConvertEnum(string str)
    {
        if (string.IsNullOrEmpty(str))
            return string.Empty;
        var result = string.Empty;
        foreach (var c in str)
            if (c.ToString() != c.ToString().ToLowerInvariant())
                result += " " + c.ToString();
            else
                result += c.ToString();

        // 确保没有空格（例如，当首字母是大写时）
        result = result.TrimStart();
        return result;
    }

    /// <summary>
    /// 计算两个日期之间的年份差异。
    /// </summary>
    /// <param name="startDate">起始日期。</param>
    /// <param name="endDate">结束日期。</param>
    /// <returns>年份差异。</returns>
    public static int GetDifferenceInYears(DateTime startDate, DateTime endDate)
    {
        // 参考来源：http://stackoverflow.com/questions/9/how-do-i-calculate-someones-age-in-c
        // 假设你要计算西方的年龄，而不是使用东亚的年龄计算方法。
        var age = endDate.Year - startDate.Year;
        if (startDate > endDate.AddYears(-age))
            age--;
        return age;
    }

    /// <summary>
    /// 使用当前线程文化的约定，将指定的年、月、日解析为 DateTime 对象。
    /// </summary>
    /// <param name="year">年份。</param>
    /// <param name="month">月份。</param>
    /// <param name="day">日期。</param>
    /// <returns>DateTime 对象实例。</returns>
    public static DateTime? ParseDate(int? year, int? month, int? day)
    {
        if (!year.HasValue || !month.HasValue || !day.HasValue)
            return null;

        DateTime? date = null;
        try
        {
            date = new DateTime(year.Value, month.Value, day.Value, CultureInfo.CurrentCulture.Calendar);
        }
        catch { }
        return date;
    }

    /// <summary>
    /// 正则表达式
    /// </summary>
    /// <param name="str">字符串</param>
    /// <param name="pattern">正则表达式</param>
    /// <returns></returns>
    public static string MatchString(string str, string pattern)
    {
        Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
        Match match = regex.Match(str);

        if (match.Success)
        {
            return match.Groups[1].Value;
        }
        else
        {
            return null;
        }
    }

    /// <summary>
    /// 获取应用于指定类型的特定类型 T 的属性。
    /// </summary>
    /// <typeparam name="T">要检索的属性的类型。</typeparam>
    /// <param name="type">应用了属性的类型。</param>
    /// <returns>如果找到类型为 T 的属性，则返回该属性；否则返回 null。</returns>
    public static T GetAttribute<T>(Type type) where T : Attribute
    {
        return (T)Attribute.GetCustomAttribute(type, typeof(T));
    }

    #endregion

    #region 属性

    /// <summary>
    /// 获取或设置默认的文件提供程序。
    /// </summary>
    public static IAiBLSmartEduFileProvider DefaultFileProvider { get; set; }

    #endregion
}
